﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using OverStore.Runtime.ObjectRepository;

namespace OverStore.Runtime.CollectionPersisting
{
    /// <summary>
    /// Changes tracker uses list of identifiers of objects from collection.
    /// </summary>
    /// <typeparam name="TInstance">The type of the instance.</typeparam>
    public sealed class DumpCollectionPersisterChangesTracker<TInstance>
        where TInstance : class
    {
        /// <summary>
        /// Because item identifier may be changed, maintain map by item instance using Reference equality comparsion for items as well as for collections.
        /// </summary>
        private readonly Dictionary<IEnumerable<TInstance>, Dictionary<TInstance, int>> _CollectionRegistry =
            new Dictionary<IEnumerable<TInstance>, Dictionary<TInstance, int>>(ReferenceEqualityComparer<IEnumerable<TInstance>>.Instance);

        /// <summary>
        /// Gets the deleted items.
        /// </summary>
        /// <param name="collection">The collection.</param>
        /// <returns>The iteratror thourght deleted items.</returns>
        public IEnumerable<TInstance> GetDeletedItems(IEnumerable<TInstance> collection)
        {
            var contentDictionary = collection.ToDictionary(i => i, ReferenceEqualityComparer<TInstance>.Instance);
            Dictionary<TInstance, int> dump;
            if (_CollectionRegistry.TryGetValue(collection, out dump))
            {
                foreach (var item in dump.Keys)
                    if (!contentDictionary.ContainsKey(item))
                        yield return item;
            }
        }

        /// <summary>
        /// Register item as belonged to collection.
        /// </summary>
        /// <param name="collection">The collection owns the item.</param>
        /// <param name="item">The item added to collection.</param>
        /// <exception cref="PersistingRuntimeException">The error occured during registering collection item.</exception>
        public void RegisterItem(IEnumerable<TInstance> collection, TInstance item)
        {
            if (!_CollectionRegistry.ContainsKey(collection))
                _CollectionRegistry.Add(collection, new Dictionary<TInstance, int>(ReferenceEqualityComparer<TInstance>.Instance));
            var items = _CollectionRegistry[collection];
            items[item] = 0;
        }

        /// <summary>
        /// Reject changes made to collection. Method clears all information
        /// about changes for specified collection and it is tracted as unchanged.
        /// </summary>
        /// <param name="collection">The collection to reject it's changes.</param>
        /// <exception cref="PersistingRuntimeException">The error occured due reject changes.</exception>
        public void RejectChanges(IEnumerable<TInstance> collection)
        {
            _CollectionRegistry.Remove(collection);
        }

        /// <summary>
        /// Track the state of the collection being attached.
        /// </summary>
        /// <param name="collection">The collection being attached.</param>
        /// <param name="getDeletedItems"><para>Delegate allows to retrieve deleted items of specified collection.</para>
        /// 	<para>The items in sequence returned by delegate must be already attached.</para></param>
        public void Attach(IEnumerable<TInstance> collection, Func<IEnumerable<TInstance>, IEnumerable<TInstance>> getDeletedItems)
        {
            Debug.Assert(collection != null, "Attaching null collection.");
            Debug.Assert(getDeletedItems != null, "GetDeletedItems delegate is null.");

            // If collection already registered, do nothing.
            if (_CollectionRegistry.ContainsKey(collection))
                return;

            Dictionary<TInstance, int> itemRegistry;
            if (!_CollectionRegistry.TryGetValue(collection, out itemRegistry))
            {
                itemRegistry = new Dictionary<TInstance, int>(ReferenceEqualityComparer<TInstance>.Instance);
                _CollectionRegistry.Add(collection, itemRegistry);
            }

            itemRegistry.Clear();
            var content = collection.Union(getDeletedItems(collection), ReferenceEqualityComparer<TInstance>.Instance);
            foreach (var item in content)
                itemRegistry.Add(item, 0);
        }
    }
}